package com.ghp.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.ghp.common.api.InnerInterfaceInfoService;
import com.ghp.common.api.InnerUserInterfaceInfoService;
import com.ghp.common.api.InnerUserService;
import com.ghp.common.exception.BusinessException;
import com.ghp.common.model.entity.User;
import com.ghp.common.utils.ThrowUtils;
import com.ghp.common.utils.response.ResponseStatus;
import com.ghp.feign.utils.SignGeneratorUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.ghp.common.constants.ErrorMessageConstants.*;

/**
 * @author ghp
 * @title 全局拦截器
 * @description
 */
@Component
@Slf4j
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @DubboReference
    private InnerUserService innerUserService;

    @DubboReference
    private InnerInterfaceInfoService innerInterfaceInfoService;

    @DubboReference
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;

    /**
     * 请求染色头
     */
    private static final String DYE_DATA_HEADER = "X-Dye-Data";
    /**
     * 染色数据
     */
    private static final String DYE_DATA_VALUE = "ghp";

    /**
     * IP白名单
     */
    private static final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1", "47.120.37.53", "localhost");


    /**
     * 拦截所有请求
     *
     * @param exchange
     * @param chain
     * @return
     */

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 准备要用的参数
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 打印请求日志
        printRequestLog(request);

        // 访问控制 - 黑白名单
        String remoteAddress = request.getRemoteAddress().getHostName();
        if (!IP_WHITE_LIST.contains(remoteAddress)) {
            // 不再白名单中的，直接拦截
            log.error("访问IP不再白名单");
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }

        // 参数校验、请求鉴权
        User user = validateAndAuthenticate(request);

        // 判断接口是否存在、判断接口是否有调用次数
        String method = request.getHeaders().get("method").get(0);
        String path = request.getPath().value();
        Long interfaceInfoId = innerInterfaceInfoService.checkInterfaceExistAndLeftNum(path, method, user.getId());

        // 处理响应
        return handleResponse(exchange, chain, user.getId(), interfaceInfoId);
    }

    /**
     * 参数校验、请求鉴权
     */
    private User validateAndAuthenticate(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String accessKey = headers.getFirst("accessKey");
        String body = headers.getFirst("body");
        body = new String(body.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        String sign = headers.getFirst("sign");
        String nonce = headers.getFirst("nonce");
        String timestamp = headers.getFirst("timestamp");
        // 判断参数是否为空
        if (StrUtil.hasBlank(accessKey, body, sign, nonce, timestamp)) {
            log.error("参数为空");
            throw new BusinessException(PARAMS_NOT_NULL);
        }
        // 判断accessKey是否合法
        User user = innerUserService.getInvokeUser(accessKey);
        if (user == null || !user.getAccessKey().equals(accessKey)) {
            log.error("accessKey不合法");
            throw new BusinessException(ACCESS_KEY_ILLEGAL);
        }
        // 判断签名是否合法
        String secretKey = user.getSecretKey();
        String serverSign = SignGeneratorUtils.generateSign(body, secretKey);
        if (!StrUtil.equals(sign, serverSign)) {
            // 服务端的签名和客户端的签名不一致
            log.error("服务端的签名和客户端的签名不一致");
            throw new BusinessException(SIGNATURE_INCONSISTENCY);
        }
        // 判断时间是否有效
        if ((System.currentTimeMillis() - Long.parseLong(timestamp)) > 1000 * 60 * 5) {
            // 当前请求超时（超过了5分钟）
            log.error("当前请求超时（超过了5分钟）");
            throw new BusinessException(ResponseStatus.CLIENT_USER_REQUEST_TIMEOUT);
        }
        return user;
    }

    /**
     * 打印请求日志
     */
    private static void printRequestLog(ServerHttpRequest request) {
        log.info("CustomGlobalFilter被执行了");
        log.info("请求id: {}", request.getId());
        log.info("请求路径: {}", request.getPath());
        log.info("请求方法: {}", request.getMethod());
        log.info("请求参数: {}", request.getQueryParams());
        log.info("请求头: {}", request.getHeaders());
        log.info("请求IP: {}", request.getRemoteAddress().getHostName());
        log.info("请求URI: {}", request.getURI());
    }

    /**
     * 处理响应（由于filter是异步响应的，所以这里需要使用ServerHttpResponseDecorator完成响应后的逻辑处理）
     */
    private Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain,
                                      Long userId, Long interfaceInfoId) {
        try {
            // 初始化Response对象
            ServerHttpResponse response = exchange.getResponse();
            DataBufferFactory bufferFactory = response.bufferFactory();
            HttpStatus statusCode = response.getStatusCode();
            // 获取ServerHttpResponseDecorator对象（利用装饰器模式对原始的Response进行增强）
            ServerHttpResponseDecorator decoratedResponse = getServerHttpResponseDecorator(
                    exchange, chain, userId, interfaceInfoId, response, bufferFactory, statusCode);
            // 流量染色，只有携带了染色数据的请求才能被 api-hub-interfas 模块响应
            ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
                    .header(DYE_DATA_HEADER, DYE_DATA_VALUE)
                    .build();
            ServerWebExchange serverWebExchange = exchange.mutate()
                    .request(modifiedRequest)
                    .response(decoratedResponse)
                    .build();

            // 设置 response 对象为装饰过的
            return chain.filter(serverWebExchange);
        } catch (Exception e) {
            log.error("网关发生异常，异常为: {}", e);
            return chain.filter(exchange);
        }
    }

    /**
     * 获取 ServerHttpResponseDecorator 对象
     */
    private ServerHttpResponseDecorator getServerHttpResponseDecorator(
            ServerWebExchange exchange, GatewayFilterChain chain, Long userId, Long interfaceInfoId,
            ServerHttpResponse response, DataBufferFactory bufferFactory, HttpStatus statusCode) {
        return new ServerHttpResponseDecorator(response) {
            // 等调用完的接口后才会执行
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                // 判断接口是否调用成功
                if (statusCode == HttpStatus.OK) {
                    // 接口响应成功
                    log.info("body instanceof Flux: {}", (body instanceof Flux));
                    // body如果是否是Flux的实例
                    if (body instanceof Flux) {
                        // body 是 Flux 的实例说明接口正常响应了
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        // 往返回值里面写数据
                        return super.writeWith(fluxBody.map(dataBuffer -> {
                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            DataBufferUtils.release(dataBuffer);
                            // 9、构建并响应日志
                            List<Object> rspArgs = new ArrayList<>();
                            rspArgs.add(response.getStatusCode());
                            String data = new String(content, StandardCharsets.UTF_8);// data
                            rspArgs.add(data);
                            log.info(" 状态码:{} 响应数据:{}", rspArgs.toArray());
                            // 10、调用成功，接口调用次数+1、接口剩余调用次数-1
                            boolean isSuccess = innerUserInterfaceInfoService.invokeCount(interfaceInfoId, userId);
                            ThrowUtils.throwIf(!isSuccess, INTERFACE_NOT_REMAIN);
                            // 返回响应成功的数据
                            return bufferFactory.wrap(content);
                        }));
                    } else {
                        log.error("响应异常，状态码:{}", getStatusCode());
                    }
                    // 返回响应失败的数据
                    return super.writeWith(body);
                }
                // 响应错误，则仍然放行，交给后面的模块处理（降级处理返回数据）
                return chain.filter(exchange);
            }
        };
    }

    /**
     * 配置拦截器的优先级，值越小优先级越高，拦截器默认的优先级是 Integer.MAX_VALUE
     *
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }
}
